<!DOCTYPE html>
<html lang="ch">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>5.加载纹理</title>
</head>

<body onload="main()">
  <canvas id="glcanvas" width="640" height="480">
    你的浏览器似乎不支持或者禁用了HTML5 <code>&lt;canvas&gt;</code> 元素.
  </canvas>

  <script src="./static/js/gl-matrix-min.js"></script>

  <script>
    /**
     * 非2的幂纹理
     * 一般来说，宽和高都是2的幂的纹理使用是最理想的。
     * 2的幂纹理在渲染内存里的存储更高效，而且对它们的使用限制也更少
     * 
     * 1.映射纹理到面
    */

    var mat4 = glMatrix.mat4;
    var cubeRotation = 0.0;

    function main() {
      var canvas = document.getElementById('glcanvas')
      var gl = canvas.getContext("webgl")

      if (!gl) {
        alert('不支持webgl')
        return;
      }

      const vsSource = `
      attribute vec4 aVertexPosition;
    attribute vec2 aTextureCoord;

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    varying highp vec2 vTextureCoord;

    void main(void) {
      gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
      vTextureCoord = aTextureCoord;
    }
      `;

      const fsSource = `
      varying highp vec2 vTextureCoord;

    uniform sampler2D uSampler;

    void main(void) {
      gl_FragColor = texture2D(uSampler, vTextureCoord);
    }
      `;

      const shaderProgram = initShaderProgram(gl, vsSource, fsSource)

      const programInfo = {
        program: shaderProgram,
        attribLocations: {
          vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
          textureCoord: gl.getAttribLocation(shaderProgram, 'aTextureCoord'),
        },
        uniformLocations: {
          projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'),
          modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'),
          uSampler: gl.getUniformLocation(shaderProgram, 'uSampler'),
        }
      };

      const buffers = initBuffers(gl)

      const texture = loadTexture(gl, './static/img/FireFox.png');

      var then = 0;
      // 
      function render(now) {
        now *= 0.001
        const deltaTime = now - then
        then = now

        drawScene(gl, programInfo, buffers, texture, deltaTime)
        cubeRotation += deltaTime;

        requestAnimationFrame(render)
      }

      requestAnimationFrame(render);
    }


    // 初始化着色器
    function initShaderProgram(gl, vsSource, fsSource) {
      const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource)
      const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);

      const shaderProgram = gl.createProgram()
      gl.attachShader(shaderProgram, vertexShader)
      gl.attachShader(shaderProgram, fragmentShader)

      gl.linkProgram(shaderProgram)

      if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
        alert('初始化着色器失败')
        return null
      }
      return shaderProgram;
    }

    function loadShader(gl, type, source) {
      const shader = gl.createShader(type)
      gl.shaderSource(shader, source)
      gl.compileShader(shader)

      if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        alert('初始化着色器失败')
        gl.deleteShader(shader);
        return null
      }
      return shader
    }


    function initBuffers(gl) {
      const positionBuffer = gl.createBuffer()
      gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)

      // 定义立方体顶点位置
      const positions = [
        -1.0, -1.0, 1.0,
        1.0, -1.0, 1.0,
        1.0, 1.0, 1.0,
        -1.0, 1.0, 1.0,

        // Back face
        -1.0, -1.0, -1.0,
        -1.0, 1.0, -1.0,
        1.0, 1.0, -1.0,
        1.0, -1.0, -1.0,

        // Top face
        -1.0, 1.0, -1.0,
        -1.0, 1.0, 1.0,
        1.0, 1.0, 1.0,
        1.0, 1.0, -1.0,

        // Bottom face
        -1.0, -1.0, -1.0,
        1.0, -1.0, -1.0,
        1.0, -1.0, 1.0,
        -1.0, -1.0, 1.0,

        // Right face
        1.0, -1.0, -1.0,
        1.0, 1.0, -1.0,
        1.0, 1.0, 1.0,
        1.0, -1.0, 1.0,

        // Left face
        -1.0, -1.0, -1.0,
        -1.0, -1.0, 1.0,
        -1.0, 1.0, 1.0,
        -1.0, 1.0, -1.0,
      ];


      gl.bufferData(gl.ARRAY_BUFFER,
        new Float32Array(positions),
        gl.STATIC_DRAW)


      // 映射纹理到面
      const textureCoordBuffer = gl.createBuffer()
      gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordBuffer)

      const textureCoordinates = [
        // Front
        0.0, 0.0,
        1.0, 0.0,
        1.0, 1.0,
        0.0, 1.0,
        // Back
        0.0, 0.0,
        1.0, 0.0,
        1.0, 1.0,
        0.0, 1.0,
        // Top
        0.0, 0.0,
        1.0, 0.0,
        1.0, 1.0,
        0.0, 1.0,
        // Bottom
        0.0, 0.0,
        1.0, 0.0,
        1.0, 1.0,
        0.0, 1.0,
        // Right
        0.0, 0.0,
        1.0, 0.0,
        1.0, 1.0,
        0.0, 1.0,
        // Left
        0.0, 0.0,
        1.0, 0.0,
        1.0, 1.0,
        0.0, 1.0,
      ];

      gl.bufferData(gl.ARRAY_BUFFER,
        new Float32Array(textureCoordinates),
        gl.STATIC_DRAW);

      // Build the element array buffer; this specifies the indices
      // into the vertex arrays for each face's vertices.

      const indexBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);

      // This array defines each face as two triangles, using the
      // indices into the vertex array to specify each triangle's
      // position.

      const indices = [
        0, 1, 2, 0, 2, 3,    // front
        4, 5, 6, 4, 6, 7,    // back
        8, 9, 10, 8, 10, 11,   // top
        12, 13, 14, 12, 14, 15,   // bottom
        16, 17, 18, 16, 18, 19,   // right
        20, 21, 22, 20, 22, 23,   // left
      ];

      // Now send the element array to GL

      gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,
        new Uint16Array(indices), gl.STATIC_DRAW);


      return {
        position: positionBuffer,
        textureCoord: textureCoordBuffer,
        indices: indexBuffer,
      }
    }


    // 渲染场景
    function drawScene(gl, programInfo, buffers, texture) {
      gl.clearColor(0.0, 0.0, 0.0, 1.0);
      gl.clearDepth(1.0)
      gl.enable(gl.DEPTH_TEST)
      gl.depthFunc(gl.LEQUAL)

      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)


      const fieldOfView = 45 * Math.PI / 180
      const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight
      const zNear = 0.1
      const zFar = 100.0

      const projectionMatrix = mat4.create()

      mat4.perspective(projectionMatrix,
        fieldOfView,
        aspect,
        zNear,
        zFar
      )

      const modelViewMatrix = mat4.create()

      mat4.translate(modelViewMatrix,  //目的地矩阵
        modelViewMatrix, // 翻转矩阵
        [-0.0, 0.0, -6.0] // 翻转量
      )

      // 将modelViewMatrix的当前值cubeRotation绕Z轴旋转
      mat4.rotate(modelViewMatrix,
        modelViewMatrix,
        cubeRotation,
        [0, 0, 1])

      mat4.rotate(modelViewMatrix,
        modelViewMatrix,
        cubeRotation * .7,
        [1, 1, 0])


      {
        const numComponents = 3  //每次迭代提取2个值
        const type = gl.FLOAT     // buffer 类型是32bit float
        const normalize = false   // 不使用标准化
        const stride = 0           //从一组值到下一组值需要多少字节
        const offset = 0           //从缓冲区内多少字节开始  

        // setMatrixUniforms();
        // 绘制正方形时使用颜色
        gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position)
        gl.vertexAttribPointer(
          programInfo.attribLocations.vertexPosition,
          numComponents,
          type,
          normalize,
          stride,
          offset
        )
        gl.enableVertexAttribArray(
          programInfo.attribLocations.vertexPosition
        )
      }

      // 初始化颜色属性
      {
        const numComponents = 2   //每次迭代提取4个值
        const type = gl.FLOAT     // buffer 类型是32bit float
        const normalize = false   // 不使用标准化
        const stride = 0           //从一组值到下一组值需要多少字节
        const offset = 0           //从缓冲区内多少字节开始  

        gl.bindBuffer(gl.ARRAY_BUFFER, buffers.textureCoord);
        gl.vertexAttribPointer(
          programInfo.attribLocations.textureCoord,
          numComponents,
          type,
          normalize,
          stride,
          offset);
        gl.enableVertexAttribArray(
          programInfo.attribLocations.textureCoord);
      }

      // 使用立方体顶点索引数据来渲染
      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.indices)

      gl.useProgram(programInfo.program)

      gl.uniformMatrix4fv(
        programInfo.uniformLocations.projectionMatrix,
        false,
        projectionMatrix
      )
      gl.uniformMatrix4fv(
        programInfo.uniformLocations.modelViewMatrix,
        false,
        modelViewMatrix
      )

      // 告诉WebGL我们想要影响纹理单位0
      gl.activeTexture(gl.TEXTURE0);

      // 将纹理绑定到纹理单元0
      gl.bindTexture(gl.TEXTURE_2D, texture);

      // 告诉着色器我们将纹理绑定到纹理单元0  
      gl.uniform1i(programInfo.uniformLocations.uSampler, 0);



      {
        const vertexCount = 36;
        const type = gl.UNSIGNED_SHORT;
        const offset = 0;
        gl.drawElements(gl.TRIANGLES, vertexCount, type, offset);
      }
    }

    // 加载纹理
    function loadTexture(gl, url) {
      // 创建一个GL纹理对象
      const texture = gl.createTexture();
      gl.bindTexture(gl.TEXTURE_2D, texture);

      // Because images have to be download over the internet
      // they might take a moment until they are ready.
      // Until then put a single pixel in the texture so we can
      // use it immediately. When the image has finished downloading
      // we'll update the texture with the contents of the image.
      const level = 0;
      const internalFormat = gl.RGBA;
      const width = 1;
      const height = 1;
      const border = 0;
      const srcFormat = gl.RGBA;
      const srcType = gl.UNSIGNED_BYTE;
      const pixel = new Uint8Array([0, 0, 255, 255]);  // opaque blue
      // 把已经加载的图片图形数据写到纹理
      gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
        width, height, border, srcFormat, srcType,
        pixel);

      const image = new Image();
      image.onload = function () {
        // 新创建的纹理对象绑定到 gl.TEXTURE_2D 来让它成为当前操作纹理。
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
          srcFormat, srcType, image);

        // WebGL1 has different requirements for power of 2 images
        // vs non power of 2 images so check if the image is a
        // power of 2 in both dimensions.
        if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
          // Yes, it's a power of 2. Generate mips.
          gl.generateMipmap(gl.TEXTURE_2D);
        } else {
          // No, it's not a power of 2. Turn of mips and set
          // wrapping to clamp to edge
          gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
          gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
          gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);


          //  // 纹理过滤器，过滤器用来控制当图片缩放时像素如何生成如何插值。
          // // 在这个例子里，我们对图片放大使用的是线性过滤，而对图片缩小使用的是多级渐进纹理过滤
          // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
          // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR_MIPMAP_NEAREST)
          // // 生成多级渐进纹理
          // gl.generateMipmap(gl.TEXTURE_2D)
          // // null 来告诉 WebGL 我们对当前纹理的操作已经结束了
          // gl.bindTexture(gl.TEXTURE_2D, null)
        }
      };
      image.src = url;

      return texture;
    }


    function isPowerOf2(value) {
      return (value & (value - 1)) == 0;
    }

  </script>
</body>

</html>